package com.google.code.yargon.ui.component;

import java.nio.FloatBuffer;

import org.apache.log4j.Logger;
import org.lwjgl.BufferUtils;

import com.google.code.yargon.Yargon;
import com.google.code.yargon.level.*;
import com.google.code.yargon.ui.*;
import com.google.code.yargon.engine.AI;
import com.google.code.yargon.engine.Creature;
import com.google.code.yargon.engine.Player;
import com.google.code.yargon.util.LogUtil;
import com.google.code.yargon.util.YargonUtil;

import static com.google.code.yargon.util.Constants.*;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.ARBBufferObject.*;
import static org.lwjgl.opengl.ARBVertexBufferObject.*;

/**
 * Interface component for Level, stores and displays the game level
 * 
 * @author Mika Myllynen
 *
 */
public class LevelComponent extends BufferedComponent {

	private LevelArea level;
	private TileSet ts;
	private Player pc;
	
	private Logger log = LogUtil.getInstance();
	
	public LevelComponent(int x, int y, LevelArea level, TileSet ts, Player pc) {
		super((Yargon.getInstance().getWindowWidth() / 2) -  pc.getLocation().getX() * ts.getSize(), 
			  (Yargon.getInstance().getWindowHeight() / 2) -  pc.getLocation().getY() * ts.getSize(), 
			  level.getWidth() * ts.getSize(), level.getHeight() * ts.getSize(), 1.0f);
		this.level = level;
		this.ts = ts;
		this.pc = pc;
	}	
	
	/**
	 * Get correct wall tile for given position
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	private Tile getWallTileAt(int x, int y) {	
		
		Tile t = null;
		Form f = new Form(level.subArea(x - 1, y - 1, 3, 3));
		
		switch(f.getFormType(WallBlock.class, DoorBlock.class)) {
		case Form.FORM_H:    	t = ts.getTile(TileSet.TILE_WALL_H); 	break;
		case Form.FORM_V: 		t = ts.getTile(TileSet.TILE_WALL_V); 	break;
		case Form.FORM_C_NE: 	t = ts.getTile(TileSet.TILE_WALL_C_NE); break;
		case Form.FORM_C_SE: 	t = ts.getTile(TileSet.TILE_WALL_C_SE); break;
		case Form.FORM_C_SW: 	t = ts.getTile(TileSet.TILE_WALL_C_SW); break;
		case Form.FORM_C_NW: 	t = ts.getTile(TileSet.TILE_WALL_C_NW); break;
		case Form.FORM_CROSS: 	t = ts.getTile(TileSet.TILE_WALL_X); 	break;
		case Form.FORM_T_N: 	t = ts.getTile(TileSet.TILE_WALL_T_N); 	break;
		case Form.FORM_T_E: 	t = ts.getTile(TileSet.TILE_WALL_T_E); 	break;
		case Form.FORM_T_S: 	t = ts.getTile(TileSet.TILE_WALL_T_S); 	break;
		case Form.FORM_T_W: 	t = ts.getTile(TileSet.TILE_WALL_T_W); 	break;
		}
		
		return t;
	} 
	
	/**
	 * Get correct door tile for given position
	 * @param x
	 * @param y
	 * @return
	 */
	private Tile getDoorTileAt(int x, int y) {
		
		Tile t = null;
		DoorBlock db = (DoorBlock)level.get(x, y);
		
		// Horizontal door?
		if(db.getAlignment() == HORIZONTAL) {
			if(db.getDurability() == 0) {
				t = ts.getTile(TileSet.TILE_DOOR_H_BROKEN);
			}
			else {
				if(db.isOpen()) {
					t = ts.getTile(TileSet.TILE_DOOR_H_OPEN);
				} else {
					t = ts.getTile(TileSet.TILE_DOOR_H_CLOSED);
				}
			}
		} 
		
		// Vertical door
		else if(db.getAlignment() == VERTICAL) {
			if(db.getDurability() == 0) {
				t = ts.getTile(TileSet.TILE_DOOR_V_BROKEN);
			}
			else {
				if(db.isOpen()) {
					t = ts.getTile(TileSet.TILE_DOOR_V_OPEN);
				} else {
					t = ts.getTile(TileSet.TILE_DOOR_V_CLOSED);
				}
			}
		}
		return t;
	}
	
	/** 
	 * Get correct tunnel tile for given position
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	private Tile getTunnelTileAt(int x, int y) {
		
		Tile t = null;
		/*
		TunnelBlock tb = (TunnelBlock)level.get(x, y);
		switch(tb.getType()) {
		case Form.FORM_H: t = ts.getTile(TileSet.TILE_TUNNEL_H); break;
		case Form.FORM_V: t = ts.getTile(TileSet.TILE_TUNNEL_V); break;
		case Form.FORM_NE: t = ts.getTile(TileSet.TILE_TUNNEL_CURVE_NE); break;
		case Form.FORM_SE: t = ts.getTile(TileSet.TILE_TUNNEL_CURVE_SE); break;
		case Form.FORM_SW: t = ts.getTile(TileSet.TILE_TUNNEL_CURVE_SW); break;
		case Form.FORM_NW: t = ts.getTile(TileSet.TILE_TUNNEL_CURVE_NW); break;						
		} */
		t = ts.getTile(TileSet.TILE_FLOOR_TUNNEL); // FIXED: uses tunnelfloor tile now
		
		return t;
	}
		
	@Override
	protected void render() {
		
		int rm = RenderEngine.getRenderMode();
		long st = YargonUtil.getTime();
		
		LevelState levelState = pc.getLevelState();
			
		FloatBuffer vtxData = null;
		FloatBuffer texData = null;
		FloatBuffer clrData = null;
				
		// *RENDERMODE* Vertex arrays only, use separate arrays for vertex, texture and color data
		if(rm == 0) {
			vtxData = BufferUtils.createFloatBuffer(level.getWidth() * level.getHeight() * 4 * 3);
			texData = BufferUtils.createFloatBuffer(level.getWidth() * level.getHeight() * 4 * 2);
			clrData = BufferUtils.createFloatBuffer(level.getWidth() * level.getHeight() * 4 * 4);
		} 
		
		// *RENDERMODE* VBO, use single interleaved array
		else {
			vtxData = BufferUtils.createFloatBuffer(level.getWidth() * level.getHeight() * 4 * 9);
		}
						
		for (int y = 0; y < level.getHeight(); y++) {
			for (int x = 0; x < level.getWidth(); x++) {
						
				float r = 1.0f;
				float g = 1.0f;
				float b = 1.0f;
				float a = 1.0f;;
				
				if(Yargon.getInstance().getDeveloperMode() == false) {

					// Color according to visibility / knowledge of area
					r = 1.0f;
					g = 1.0f;
					b = 1.0f;
					a = 0.3f;
					
					// Visible?
					if(levelState.isVisible(x, y)) {
						g = 0.95f;
						b = 0.75f;
						a = levelState.getLightLevel(x, y);
					} 
					
					// Not known? Skip
					else if(! levelState.isKnown(x, y)) {
						continue;
					}
				}
							
				Tile t = null;
				
				// Player? 
				if(x == pc.getLocation().getX() && y == pc.getLocation().getY()) {
					t = ts.getTile(TileSet.TILE_CHARACTER);
					r = 1.0f;
					g = 1.0f;
					b = 1.0f;
					a = 1.0f;
				}
				
				// Determine tile according to block type and placement
				else {	
					// Floor block?
					if(level.isFloor(x, y)) {
						t = ts.getTile(TileSet.TILE_FLOOR_ROOM);
					} 
					
					// Wall block? determine tile according to wall position
					else if(level.isWall(x, y)) {
						t = getWallTileAt(x,y);
						r = 1.0f;
						g = 1.0f;
						b = 1.0f;
						a = 0.7f;
					}
					
					// Tunnel block?
					else if(level.isTunnel(x, y)) {
						t = getTunnelTileAt(x, y);
					} 
		
					// Door block? 
					else if(level.isDoor(x, y)) {
						t = getDoorTileAt(x, y);
					}
																	
					// Something else, no need to fill vertex data for undefined blocks
					else {
						t = ts.getTile(TileSet.TILE_UNDEFINED);
						continue;
					}
				}
				
				// Quad coordinates
				float qx = x * ts.getSize();
				float qy = y * ts.getSize();
				float qw = ts.getSize();
				float qh = ts.getSize();
					
				// *RENDERMODE* Vertex arrays
				if(rm == 0) {
					vtxData.put(new float[] {
							qx, qy, 0,
							qx + qw, qy, 0,
							qx + qw, qy + qh, 0,
							qx, qy + qh, 0
					});
					
					texData.put(new float[] {
							t.getX(), t.getY() + t.getH(),
							t.getX() + t.getW(), t.getY() + t.getH(),
							t.getX() + t.getW(), t.getY(),
							t.getX(), t.getY()
					});
					
					clrData.put(new float[] {
							r, g, b, a,
							r, g, b, a,
							r, g, b, a,
							r, g, b, a,
					});
				}
				
				// *RENDERMODE* VBO interleaved VVVTTCCCC
				else {
					vtxData.put(new float[] {
							qx, qy, 0, t.getX(), t.getY() + t.getH(), r, g, b, a,
							qx + qw, qy, 0, t.getX() + t.getW(), t.getY() + t.getH(), r, g, b, a,
							qx + qw, qy + qh, 0, t.getX() + t.getW(), t.getY(), r, g, b, a,
							qx, qy + qh, 0, t.getX(), t.getY(), r, g, b, a,
					});
				}
			}
		}
			
		// *RENDERMODE* Vertex arrays
		if(rm == 0) {
			vtxData.flip();
			texData.flip();
			clrData.flip();
		}
		
		// *RENDERMODE* VBO 
		else {
			vtxData.flip();
		}
			
		// Actual level rendering takes place here		
		glPushMatrix();
		glLoadIdentity();
		
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
		glEnable(GL_TEXTURE_2D);	
		glBindTexture(GL_TEXTURE_2D, ts.getTileTexture().getTextureID());
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
		// Enable arrays
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);

		// *RENDERMODE* Vertex arrays
		if(rm == 0) {
			glVertexPointer(3, 3*4, vtxData);										
			glTexCoordPointer(2, 2*4, texData);									
			glColorPointer(4, 4*4, clrData);
		} 
		
		// *RENDERMODE* VBO
		else {
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);		
			glBufferDataARB(GL_ARRAY_BUFFER_ARB, vtxData, GL_STATIC_DRAW_ARB);		
			glVertexPointer(3, GL_FLOAT, 9*4, 0);										// 9*4 byte chunks per vertex, start from first
			glTexCoordPointer(2, GL_FLOAT, 9*4, 12);									// start from 12. 
			glColorPointer(4, GL_FLOAT, 9*4, 20);										// start from 20.		
		}
			
		glDrawArrays(GL_QUADS, 0, level.getWidth() * level.getHeight() * 4);
		renderCreatures();
	
		// *RENDERMODE* VBO
		if(rm == 1) {
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
		}
		
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		
		glDisable(GL_TEXTURE_2D);	
		glDisable(GL_BLEND);
		glPopMatrix();
		
		log.trace("Rendering scene took "+(YargonUtil.getTime() - st)+" ms (using renderMode: "+rm+")");	
	}
		
	private void renderCreatures() {
		
		for(Creature c : AI.getInstance().getCreatures()) {
			
			Tile t = ts.getTile(TileSet.TILE_RAT);
				
			if(pc.getLevelState().isVisible(c.getLocation().getX(), c.getLocation().getY())) {
				
				glBegin(GL_QUADS);
				{		
					// Bottom left
					glTexCoord2f(t.getX(), t.getY() + t.getH());
					glVertex2f(c.getLocation().getX() * ts.getSize(), c.getLocation().getY() * ts.getSize());
				
					// Bottom right
					glTexCoord2f(t.getX() + t.getW(), t.getY() + t.getH());
					glVertex2f(c.getLocation().getX() * ts.getSize() + ts.getSize(), c.getLocation().getY() * ts.getSize());
			
					// Top right
					glTexCoord2f(t.getX() + t.getW(), t.getY());
					glVertex2f(c.getLocation().getX() * ts.getSize() + ts.getSize(), c.getLocation().getY() * ts.getSize() + ts.getSize());
					
					// Top left
					glTexCoord2f(t.getX(), t.getY());
					glVertex2f(c.getLocation().getX() * ts.getSize(), c.getLocation().getY() * ts.getSize() + ts.getSize());
				}	
				glEnd();	
			}
		}
	}
	
	@Override
	protected void update() {
		if(level.needRefresh()) {
			setUpdated(true);
			level.setRefresh(false);
		}
		if(pc.needRefresh()) {
			setUpdated(true);
			pc.setRefresh(false);
		}
	}
	
	@Override
	protected void onClick(String id) {
		// Nothing to listen
	}
}
